use miette::{Diagnostic, SourceSpan};
use ntex::web;
use snafu::prelude::*;
use snafu::Snafu;
use std::io;
use std::path::PathBuf;

#[derive(Snafu, Debug, Diagnostic)]
pub enum MyError {
    #[snafu(display("Unable to read configuration from {}", path.display()))]
    #[diagnostic(code(my_lib::io_error), help("读取配置失败"))]
    ReadConfiguration { source: io::Error, path: PathBuf },

    #[snafu(display("Unable to write result to {}", path.display()))]
    #[diagnostic(code(my_lib::io_error), help("写入失败"))]
    WriteResult { source: io::Error, path: PathBuf },

    #[snafu(display("not found, {}", uri))]
    #[diagnostic(code("404"), help("not found"))]
    NotFound { source: io::Error, uri: String },

    #[snafu(display("bad request, {}", uri))]
    #[diagnostic(code("400"), help("bad request"))]
    BadRequest { source: io::Error, uri: String },
}

#[derive(Snafu, Diagnostic, Debug)]
pub struct AnotherError {
    #[label("here")]
    pub at: SourceSpan,
}

#[derive(Debug, Snafu)]
#[snafu(whatever, display("Error was: {message}"))]
struct WhateverError {
    message: String,
    #[snafu(source(from(Box<dyn std::error::Error>, Some)))]
    source: Option<Box<dyn std::error::Error>>,
}

impl web::error::WebResponseError for MyError {}
impl web::error::WebResponseError for AnotherError {}
impl web::error::WebResponseError for WhateverError {}

#[web::get("/1")]
async fn index() -> Result<String, MyError> {
    let path = "0'";
    let configuration = std::fs::read_to_string(path).context(ReadConfigurationSnafu { path })?;
    Ok(configuration)
}

#[web::get("/2")]
async fn index2() -> Result<String, WhateverError> {
    whatever!("")
}

#[ntex::main]
async fn main() -> io::Result<()> {
    web::HttpServer::new(|| web::App::new().service(index))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

#[cfg(test)]
mod test {
    use pike::pike;

    #[tokio::test]
    async fn test2() {
        let value = 22;
        let result = moro::async_scope!(|scope| {
            let future1 = scope.spawn(async {
                let future2 = scope.spawn(async {
                    value // access stack values that outlive scope
                });

                let v = future2.await * 2;
                v
            });

            let v = future1.await * 2;
            v
        })
        .await;
        eprintln!("{result}"); // prints 88
    }

    #[test]
    fn test1() {
        fn times(a: u32, b: u32) -> u32 {
            a * b
        }

        fn times2(n: u32) -> u32 {
            times(n, 2)
        }

        // Passes the preceding expression as the only argument of proceding function.
        let num = pike! {
          2
          |> times2
          |> times2
        };
        assert_eq!(num, 2 * 2 * 2);

        // Passes the preceding expression as the first argument of proceding function.
        // by wrapping the function in parentheses we can pass the remanining arguments by partially
        // calling the `times` as `times(?, 2)` and passing 2 as its first argument via the pipeline.
        let num = pike! {
          1
          |> (times(2))
          |> (times(3))
        };
        assert_eq!(num, 1 * 2 * 3);
    }
}
